/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/java-pdf-library-support/
 *
 * (C) Copyright 1997-2013, IDRsolutions and Contributors.
 *
 * 	This file is part of JPedal
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * ReadOnlyTextIcon.java
 * ---------------
 */
package org.jpedal.objects.acroforms.overridingImplementations;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import javax.swing.Icon;
import javax.swing.SwingConstants;

import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.acroforms.formData.ComponentData;
import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.FormStream;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.objects.raw.XObject;
import org.jpedal.utils.StringUtils;

/** this class is used to display the text fields in the defined font, but is used for readonly fields only. */
public class ReadOnlyTextIcon extends CustomImageIcon implements Icon, SwingConstants {

	private int alignment = -1;

	private static final long serialVersionUID = 8946195842453749725L;

	/** stores the root image for selected and unselected icons */
	private BufferedImage rootImage = null;
	/** stores the final image after any icon rotation */
	private BufferedImage finalImage = null;

	private PdfObject fakeObj = null;

	/** tells us if the text for this icon has chnaged and so if we need to redraw the icon */
	private boolean textChanged = false;
	private String preFontStream = "", betweenFontAndTextStream = "", afterTextStream = "", text = "";
	private String fontName = "", fontSize = "", fontCommand = "";

	/** our full command Stream */
	private String fullCommandString;

	private PdfObjectReader currentpdffile = null;
	private int subtype = -1;
	private PdfObject resources;

	/**
	 * new code to store the data to create the image when needed to the size needed offset = if 0 no change, 1 offset image, 2 invert image <br>
	 * NOTE if decipherAppObject ios not called this will cause problems.
	 */
	public ReadOnlyTextIcon(int iconRot, PdfObjectReader pdfObjectReader, PdfObject res) {
		super(iconRot);

		this.currentpdffile = pdfObjectReader;
		this.resources = res;

		// if(selObj.getObjectRefAsString().equals("128 0 R") || selObj.getObjectRefAsString().equals("130 0 R"))
		// debug = true;
	}

	/** returns the currently selected Image */
	@Override
	public Image getImage() {
		Image image;
		checkAndCreateimage();

		image = this.finalImage;

		return image;
	}

	/**
	 * draws the form to a BufferedImage the size of the Icon and returns it, uses the paintIcon method for the drawing so future changes should only
	 * be in one place
	 */
	public BufferedImage drawToBufferedImage() {
		BufferedImage bufImg = new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB);
		Graphics g = bufImg.getGraphics();
		paintIcon(null, g, 0, 0);
		g.dispose();

		return bufImg;
	}

	@Override
	public synchronized void paintIcon(Component c, Graphics g, int x, int y) {

		BufferedImage image = (BufferedImage) getImage();

		if (image == null) return;

		if (c != null && c.isEnabled()) {
			g.setColor(c.getBackground());
		}
		else {
			g.setColor(Color.gray);
		}

		Graphics2D g2 = (Graphics2D) g;
		if (this.iconWidth > 0 && this.iconHeight > 0) {

			int drawWidth = this.iconWidth;
			int drawHeight = this.iconHeight;
			if (this.displaySingle && (this.iconRotation == 270 || this.iconRotation == 90)) {
				// swap width and height so that the image is drawn in the corect orientation
				// without changing the raw width and height for the icon size
				drawWidth = this.iconHeight;
				drawHeight = this.iconWidth;
			}

			// only work out scaling if we have a dictionary of an image, as otherwise it could be a blank image (i.e. 1 x 1).
			if (this.currentpdffile != null) {
				// work out w,h which we want to draw inside our icon to maintain aspect ratio.
				float ws = (float) drawWidth / (float) image.getWidth(null);
				float hs = (float) drawHeight / (float) image.getHeight(null);
				if (ws < hs) {
					drawWidth = (int) (ws * image.getWidth(null));
					drawHeight = (int) (ws * image.getHeight(null));
				}
				else {
					drawWidth = (int) (hs * image.getWidth(null));
					drawHeight = (int) (hs * image.getHeight(null));
				}
			}

			// now work out the x,y position to keep the icon in the centre of the icon
			int posX = 0, posY = 0;
			if (this.currentpdffile != null) {
				if (this.displaySingle && (this.iconRotation == 270 || this.iconRotation == 90)) {
					posX = (this.iconHeight - drawWidth) / 2;
					posY = (this.iconWidth - drawHeight) / 2;
				}
				else {
					posX = (this.iconWidth - drawWidth) / 2;
					posY = (this.iconHeight - drawHeight) / 2;
				}
			}

			if (this.alignment == SwingConstants.LEFT) posX = 0;

			int finalRotation;
			if (this.displaySingle) {
				finalRotation = validateRotationValue(this.pageRotate - this.iconRotation);
			}
			else {
				finalRotation = this.pageRotate;
			}

			/** with new decode at needed size code the resize (drawImage) may not be needed. */
			if (finalRotation == 270) {
				g2.rotate(-Math.PI / 2);
				g2.translate(-drawWidth, 0);
				g2.drawImage(image, -posX, posY, drawWidth, drawHeight, null);
			}
			else
				if (finalRotation == 90) {
					g2.rotate(Math.PI / 2);
					g2.translate(0, -drawHeight);
					g2.drawImage(image, posX, -posY, drawWidth, drawHeight, null);
				}
				else
					if (finalRotation == 180) {
						g2.rotate(Math.PI);
						g2.translate(-drawWidth, -drawHeight);
						g2.drawImage(image, -posX, -posY, drawWidth, drawHeight, null);
					}
					else {
						g2.drawImage(image, posX, posY, drawWidth, drawHeight, null);
					}
		}
		else g2.drawImage(image, 0, 0, null);

		g2.translate(-x, -y);
	}

	private void checkAndCreateimage() {
		// check if pdf object reader is defined, as we still use opaque images which do NOT need redecoding
		if (this.currentpdffile == null) return;

		/**
		 * NOTE the image code may need changing so that we store up to a certain size image and not store large images, once the user has rescaled to
		 * a more normal size. we could store the root width and height for the 100% size and use 200% as the highest image size to keep.
		 * 
		 * if we do this the best way would be to have an object that we move the decode routine to, and then when we read the 100% values from the
		 * image object we can store them in that size.
		 */

		// define normal sizes for normal use
		int newWidth = this.iconWidth, newHeight = this.iconHeight;

		// decode images at needed size
		if (this.textChanged || this.rootImage == null || newWidth > (this.rootImage.getWidth(null)) || newHeight > (this.rootImage.getHeight(null))
				|| newWidth < (this.rootImage.getWidth(null) / MAXSCALEFACTOR) || newHeight < (this.rootImage.getHeight(null) / MAXSCALEFACTOR)) {
			// System.out.println(fakeObj.getObjectRefAsString()+" command="+fullCommandString);
			this.rootImage = FormStream.decode(this.currentpdffile, this.fakeObj, this.subtype, newWidth, newHeight, 0, 1);

			this.finalImage = FormStream.rotate(this.rootImage, this.iconRotation);

			// make text as redrawn
			this.textChanged = false;

		}// icon rotation is always defined in the constructor so we dont need to change it
	}

	/**
	 * set the font and text of the form, ie if it changes, set this and it will redraw the image. if font is null it will not be changed.
	 */
	public void setFont(String fontName, float fontSize, String fontCommand) {
		this.fontName = fontName;
		if (fontName.length() != 0) {
			this.fontSize = " " + fontSize + ' ';
		}
		else {
			this.fontSize = "";
		}
		this.fontCommand = fontCommand;
	}

	public void setText(String str) {
		if (str == null) str = "";

		if (str.equals(this.text)) return;

		this.textChanged = true;
		this.text = str;

		this.fullCommandString = this.preFontStream + this.fontName + this.fontSize + this.fontCommand + this.betweenFontAndTextStream + '('
				+ this.text + ")Tj " + this.afterTextStream;
		this.fakeObj.setDecodedStream(StringUtils.toBytes(this.fullCommandString));
	}

	public String getText() {
		return this.text;
	}

	/**
	 * decodes and saves all information needed to decode the object on the fly, the test and font can be altered with specific methods.
	 * 
	 * @return boolean true if it all worked.
	 */
	public boolean decipherAppObject(FormObject form) {
		// read the command from file if there is one
		String fontStr = "";
		PdfObject appObj = form.getDictionary(PdfDictionary.AP).getDictionary(PdfDictionary.N);
		if (appObj != null) {
			byte[] bytes = appObj.getDecodedStream();

			if (bytes != null) {
				int startTf = -1, endTf = -1, startTj, endTj = -1, end = bytes.length;

				// find index of Tf command
				for (int i = 0; i < end - 1; i++) {
					if (((char) bytes[i]) == 'T' && ((char) bytes[i + 1]) == 'f') {
						if (i + 2 >= end || bytes[i + 2] == 10 || bytes[i + 2] == 13 || bytes[i + 2] == ' ') {
							endTf = i + 2;
							break;
						}
					}
				}

				if (endTf == -1) {
					startTf = 0;
					endTf = 0;
				}
				else {
					// find beginning of Tf command
					// int strs = 0;
					// boolean strFound = false;
					for (int i = endTf - 3; i > startTf; i--) {
						// this is kept until its passed tests.
						// if(bytes[i]==' ' || bytes[i]==10 || bytes[i]==13){
						// if(strFound){
						// strs++;
						// if(strs==2){
						// startTj = i+1;//to allow for the gap
						// //should give the same index as the '/'
						// break;
						// }
						// }
						// continue;
						// }else
						if (bytes[i] == '/') {
							startTf = i;
							break;
							// }else {
							// strFound = true;
						}
					}

					// ******startTf and endTf should both have a value, and start should be before end******
				}

				// find index of Tj command
				for (int i = endTf; i < end - 1; i++) {
					if (((char) bytes[i]) == 'T' && ((char) bytes[i + 1]) == 'j') {
						if (i + 2 >= end || bytes[i + 2] == 10 || bytes[i + 2] == 13 || bytes[i + 2] == ' ') {
							endTj = i + 2;
							break;
						}
					}
				}

				if (endTj == -1) {
					startTj = endTf;
					endTj = endTf;
				}
				else {
					startTj = endTf;

					// find the start of the Tj command
					int brackets = 0;
					boolean strFound = false;
					for (int i = endTj - 3; i > startTj; i--) {
						if (bytes[i] == ' ' || bytes[i] == 10 || bytes[i] == 13) {
							if (strFound && brackets == 0) {
								// +1 as we dont want the gap we just found in our text string
								startTj = i + 1;
								break;
							}
							continue;
						}
						else
							if (bytes[i] == ')') {
								brackets++;
							}
							else
								if (bytes[i] == '(') {
									brackets--;
									if (brackets == 0 && strFound) {
										startTj = i;
										break;
									}
								}
								else {
									strFound = true;
								}
					}

					// ******* startTJ and endTj should both have a value and start should be before end ******
				}

				// find actual end of Tf including any rg or g command after the Tf.
				for (int i = endTf; i < startTj; i++) {
					if (bytes[i] == ' ' || bytes[i] == 10 || bytes[i] == 13) {
						continue;
					}
					else
						if (bytes[i] > 47 && bytes[i] < 58) {
							// number
							continue;
						}
						else {
							if (bytes[i] == 'g' && i + 1 < startTj && (bytes[i + 1] == ' ' || bytes[i + 1] == 10 || bytes[i + 1] == 13)) {
								endTf = i + 1;
								break;
							}
							else
								if (bytes[i] == 'r' && i + 2 < startTj && bytes[i + 1] == 'g'
										&& (bytes[i + 2] == ' ' || bytes[i + 2] == 10 || bytes[i + 2] == 13)) {
									endTf = i + 2;
									break;
								}
								else {
									// not what we want leave endTf as is.
									break;
								}
						}
				}

				if (endTj != endTf) {
					// there is a Tj (text)
					if (endTf == 0) {
						// we dont have a font command defined so allow for one
						this.preFontStream = new String(bytes, 0, startTj);
						this.betweenFontAndTextStream = " ";
					}
					else {
						// we have a font command
						this.preFontStream = new String(bytes, 0, startTf);
						fontStr = new String(bytes, startTf, endTf - startTf);
						this.betweenFontAndTextStream = new String(bytes, endTf, startTj - endTf);
					}
					// -3 to ignore the Tj command letters at the end as we add that ourselves.
					this.text = new String(bytes, startTj, endTj - 3 - startTj);
					this.afterTextStream = new String(bytes, endTj, bytes.length - endTj);
				}
				else {
					// theres no TJ
					if (endTf == 0) {
						// store as command1, and if not valid we deal with below with default command
						this.preFontStream = new String(bytes);
					}
					else {
						// we have a font command
						this.preFontStream = new String(bytes, 0, startTf);
						fontStr = new String(bytes, startTf, endTf - startTf);
						// add rest to middleCommand so Text can be added to end
						this.betweenFontAndTextStream = new String(bytes, endTf, bytes.length - endTf);
					}
				}
			}
		}

		// get the forms font string
		String DA = form.getTextStreamValue(PdfDictionary.DA);

		if (DA == null || DA.length() == 0) {
			if (fontStr.length() != 0) {
				// set font we have found
				form.setTextStreamValue(PdfDictionary.DA, StringUtils.toBytes(fontStr));
				FormStream.decodeFontCommandObj(fontStr, form);
			}

			// use old methods as appropriate info not present.
			return false;

		}
		else { // updated by Mark as previous code had bug
			// we replace the TF string (ie /FO_0 8 Tf) with the DA value (ie /Helv 8 Tf) to get the font name
			// this though assumes that Tm is 1 0 0 1 (scaling is done by fotnsize not matrix)
			// on sample file Tf was 1 and Tm was 8 0 0 8 so we ended up with text 8 times too big as we changed
			// /Fo_0 1 Tf to /Helv 8 Tf while not altering Tm
			// I have fixed by keeping Tf value and using /DA font part

			if (fontStr.length() == 0) // use defined DA and remove any whitespace at front (ORIGINAL version)
			fontStr = DA.trim();
			else {// get font name from DA but use original fontsize
				String fontname = DA.substring(0, DA.indexOf(' '));
				String fontsize = fontStr.substring(fontStr.indexOf(' '), fontStr.length());
				fontStr = fontname + fontsize;
				fontStr.trim();
			}

		}

		// create a fake XObject to make use of the code we already have to generate image
		this.fakeObj = new XObject(form.getObjectRefAsString()); // value does not matter

		// do not think we need but here for completeness
		// XObject.setFloatArray(PdfDictionary.Matrix,new float[]{1,0,0,1,0,0});

		// forms can have resources (Fonts, XOBjects, etc) which are in the DR value -
		// we store this in DefaultAcroRenderer
		if (this.resources != null) this.fakeObj.setDictionary(PdfDictionary.Resources, this.resources);

		Rectangle BBox = form.getBoundingRectangle();
		this.fakeObj.setFloatArray(PdfDictionary.BBox, new float[] { BBox.width, 0, 0, BBox.height, 0, 0 });

		this.subtype = -1; // could use PdfDictionary.Highlight for transparency

		// if no command in file.
		if (this.preFontStream.length() == 0 || !this.preFontStream.contains("BT")) {
			// build a fake command stream to decode
			this.preFontStream = "BT 0 0 0 RG 1 TFS ";
			this.betweenFontAndTextStream = " 1 0 0 1 0 0 Tm ";
			this.afterTextStream = "";
		}

		// find the start and end of the size param
		int sizeSt = fontStr.indexOf(' ');
		int sizeEn = -1;
		boolean strFound = false;
		for (int i = sizeSt; i < fontStr.length(); i++) {
			char chr = fontStr.charAt(i);
			if (chr == ' ' || chr == 10 || chr == 13) {
				if (strFound) {
					sizeEn = i;
					break;
				}
				continue;
			}
			else {
				strFound = true;
			}
		}

		float size = 12;
		if (sizeEn != -1) {
			// store the name, and command
			this.fontName = fontStr.substring(0, sizeSt);
			this.fontCommand = fontStr.substring(sizeEn);
			size = Float.parseFloat(fontStr.substring(sizeSt, sizeEn));
		}

		// store the seperate font attributes
		if (this.fontName.length() == 0) {
			Font textFont = form.getTextFont();
			this.fontName = '/' + textFont.getFontName();
			this.fontCommand = "Tf ";
		}

		// check if font size needs autosizing
		if (size == 0 || size == -1) {
			// call our calculate routine to work out a good size
			size = ComponentData.calculateFontSize(BBox.height, BBox.width, false, this.text);
		}
		this.fontSize = " " + size + ' ';

		return true;
	}

	public void setAlignment(int alignment) {
		this.alignment = alignment;
	}

	/** generates higher quality images */
	public void setPrinting(boolean print, int multiplier) {

		checkAndCreateimage();
	}
}
